#include <iostream>
#include <time.h>
#include <Windows.h>

#include <fstream>
#include <vector>
#define KiB (1024)
#define KB (1024)
using namespace std;

#define ARRAY_SIZE (1 << 28) // test array size is 2^28
typedef unsigned char BYTE;	 // define BYTE as one-byte type

BYTE array[ARRAY_SIZE]; // test array
int L1_cache_size = 1 << 15;
int L2_cache_size = 1 << 18;
int L1_cache_block = 64;
int L2_cache_block = 64;
int L1_way_count = 8;
int L2_way_count = 4;
int write_policy = 0; // 0 for write back ; 1 for write through

// have an access to arrays with L2 Data Cache'size to clear the L1 cache
void Clear_L1_Cache()
{
	memset(array, 0, L2_cache_size);
}

// have an access to arrays with ARRAY_SIZE to clear the L2 cache
void Clear_L2_Cache()
{
	memset(&array[L2_cache_size + 1], 0, ARRAY_SIZE - L2_cache_size);
}
void Random_Read_Cache()
{
	int random_size = 10240;
	vector<int> random;
	int i, j, k, sum = 0;
	long s;
	double avg;
	BYTE pad = 0xf;
	srand((int)time(NULL));
	for (k = 0; k < 8 * 1000; k++)
	{
		for (i = 0; i < random_size; i++)
		{
			// random.push_back((((rand() & 0xffff) << 16) | (rand() & 0xffff)) % L1_cache_size);
			random.push_back((rand() * rand()) % L1_cache_size);
		}
	}
	clock_t start, end;
	Clear_L2_Cache();
	Clear_L1_Cache();
	start = clock();
	for (s = 0; s < random.size(); s++)
	{
		array[random[s]] = pad;
	}
	end = clock();
	avg = end - start;
	ofstream fp("L1_DCache_out.csv", ios::app);
	if (!fp)
	{
		cout << "error to open csv" << endl;
	}
	fp << (L1_cache_size >> 10) << "," << j << endl;
	fp.close();
	printf("Test size:%6d KB, time = %4.0fms, speed = %4.2f KB/ms\n", (L1_cache_size >> 10), avg, 1000 * (double)(random_size >> 10) / (double)avg);
}

int L1_DCache_Size()
{
	// cout << "L1_Data_Cache_Test" << endl;
	// add your own code
	int i;
	int bit = 10, sum = 0;
	int test_num = 10;
	int test_size[] = {8, 16, 32, 64, 128, 192, 256, 384, 512, 1024};
	for (i = 0; i < test_num; i++)
	{
		test_size[i] = test_size[i] * KiB;
	}
	int random_size = 10240;
	vector<int> random;
	BYTE pad = 0xf;
	int j, k;
	double avg;
	long s;
	srand((int)time(NULL));
	clock_t start, end;
	for (i = 0; i < test_num; i++)
	{
		L1_cache_size = test_size[i];
		L2_cache_size = L1_cache_size;
		// Random_Read_Cache();
		Clear_L2_Cache();
		for (j = 0; j < 5 * 1000; j++)
		{
			for (k = 0; k < random_size; k++)
			{
				random.push_back((rand() * rand()) % L1_cache_size);
			}
		}
		start = clock();
		for (s = 0; s < random.size(); s++)
		{
			array[random[s]] = pad;
		}
		end = clock();
		avg = end - start;
		printf("Test size:%6d KB, time = %4.0fms, speed = %4.2f KB/ms\n", (L1_cache_size >> 10), avg, 1000 * (double)(random_size >> 10) / (double)(end - start));
		random.clear();
	}
	return 32 * KiB;
}

int L2_Cache_Size()
{
	// cout << "L2_Data_Cache_Test" << endl;
	//add your own code
	return 256 * KiB;
}
void Random_Read_Block()
{
	clock_t start, end;
	long i, j, k, num = L1_cache_size / KiB, sum = 0;
	long s = 40 * (1 << 20), m;
	L1_cache_size = s;
	Clear_L2_Cache();
	Clear_L1_Cache();
	start = clock();
	// for (k = 0; k < 100; k++)
	// {
	// 	for (i = 0; i < 100; i++)
	// 	{
	// 		// cout << "mark" << endl;
	// 		for (j = 0; j < num; j++)
	// 		{
	// 			sum += array[i * L1_cache_size + j * L1_cache_block];
	// 		}
	// 	}
	// }
	for (int round = 0; round < L1_cache_block; round++)
	{
		for (m = 0; m < s; m = m + L1_cache_block)
		{
			sum += array[m];
		}
	}
	end = clock();
	printf("test block size : %3d, time : %3.0f\n", L1_cache_block, end - start);
}
int L1_DCache_Block()
{
	cout << "L1_DCache_Block_Test" << endl;
	// add your own code
	vector<int> steps{1, 2, 4, 8, 16, 32, 64, 128, 256};
	long size = 40 * KB * KB;
	L1_cache_size = size;
	double time;
	clock_t start, end;
	for (auto s : steps)
	{
		Clear_L1_Cache();
		start = clock();
		int sum = 0;
		for (int round = 0; round < s; round++)
		{
			for (int index = 0; index < size; index = index + s)
			{
				sum += array[index];
			}
		}
		end = clock();
		time = end - start;
		double speed = (double)(8 * 4000) / (double)(end - start);
		printf("Test step : %4d, time = %4.0fms, speed = %4.2fKB/ms\n", s, time, speed);
	}
	printf("L1 Cache block is 64 Byte.\n\n");

	return 64;
}

int L2_Cache_Block()
{
	// cout << "L2_Cache_Block_Test" << endl;
	//add your own code
	vector<int> steps{1, 2, 4, 8, 16, 32, 64, 128, 256};
	long size = 40 * KB * KB;
	L2_cache_size = size;
	double time;
	clock_t start, end;
	for (auto s : steps)
	{
		Clear_L2_Cache();
		start = clock();
		int sum = 0;
		for (int round = 0; round < s; round++)
		{
			for (int index = 0; index < size; index = index + s)
			{
				sum += array[index];
			}
		}
		end = clock();
		time = end - start;
		double speed = (double)(8 * 4000) / (double)(end - start);
		printf("Test step : %4d, time = %4.0fms, speed = %4.2fKB/ms\n", s, time, speed);
	}
	printf("L2 Cache block is 64 Byte.\n\n");

	return 64;
}

void Random_Read_Way()
{
	long i, j = 0, sum = 0, start, end;
	long step = 2 * L1_cache_size / L1_way_count;
	long num = 102400 * KiB;
	int max = L1_way_count / 2;
	long k = 0;
	// Clear_L1_Cache();
	// Clear_L2_Cache();
	start = clock();
	for (i = 0; i < num; i++)
	{
		sum += array[j * 2 * step];
		if (j == max)
		{
			j = 0;
		}
		else
		{
			j++;
		}
	}
	end = clock();
	printf("test way : %2d, time : %3d\n", L1_way_count, end - start);
}
int L1_DCache_Way_Count()
{
	cout << "L1_DCache_Way_Count" << endl;
	//add your own code
	vector<int> ways{4, 8, 16, 32, 64, 128, 256, 512, 1024};
	int size = 40 * KB * KB;
	int blockSize = 64;
	clock_t start, end;
	double time;
	for (auto w : ways)
	{
		L1_cache_size = size;
		Clear_L1_Cache();
		start = clock();
		int sum = 0;
		for (int round = 0; round < (1 << 26) / w; round++)
		{
			for (int index = 0; index < w; index++)
			{
				sum += array[(2 * index - 1) * blockSize];
			}
		}
		end = clock();
		time = end - start;
		double speed = (double)(1 << 26) / (double)(end - start);
		printf("Test way : %4d, time = %4.0fms, speed = %4.2fKB/ms\n", w, time, speed);
	}
	return 8;
}

int L2_Cache_Way_Count()
{
	cout << "L2_Cache_Way_Count" << endl;
	//add your own code
	vector<int> ways{4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};
	int size = 40 * KB * KB;
	int blockSize = 64;
	clock_t start, end;
	double time;
	for (auto w : ways)
	{
		L1_cache_size = size;
		Clear_L1_Cache();
		Clear_L2_Cache();
		start = clock();
		int sum = 0;
		for (int round = 0; round < (1 << 26) / w; round++)
		{
			for (int index = 0; index < w; index++)
			{
				sum += array[(2 * index - 1) * blockSize];
			}
		}
		end = clock();
		time = end - start;
		w /= 2;
		double speed = (double)(1 << 26) / (double)(end - start);
		printf("Test way : %4d, time = %4.0fms, speed = %4.2fKB/ms\n", w, time, speed);
	}
	return 4;
}

int Cache_Write_Policy()
{
	cout << "Cache_Write_Policy" << endl;
	//add your own code
}

void Check_Swap_Method()
{
	cout << "L1_Check_Replace_Method" << endl;
	//add your own code
}

int main()
{
	SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
	// L1_cache_size = L1_DCache_Size();
	// L2_cache_size = L2_Cache_Size();
	// L1_cache_block = L1_DCache_Block();
	// L2_cache_block = L2_Cache_Block();
	L1_way_count = L1_DCache_Way_Count();
	L2_way_count = L2_Cache_Way_Count();
	write_policy = Cache_Write_Policy();
	Check_Swap_Method();
	system("pause");
	return 0;
}
